/**
*\file mise_en_carton.c
*\author H4201
*/

/*MODULES USED*/
#include <signal.h>
#include <string.h>
#include <mqueue.h>
#include <math.h>

#include "mise_en_carton.h"
#include "ResChaineCond.h"
#include "../interface/partage_simu.h"
#include "../interface/constantes.h"
#include "../interface/messqueue.h"
#include "../interface/mutexpartage.h"
#include "../interface/variablesglobales.h"
#include "../utils/utils.h"

/*DEFINITIONS AND MACROS*/
#define TIMEOUT 30

/*PROTOTYPES OF LOCAL FUNCTIONS*/
void arrivee_piece_handler(int parameter);
void init_mise_en_carton();
void detruire_mise_en_carton();


/*GLOBAL VARIABLES*/
/**
	boolean indiquant que l'application demande à la chaine de conditionnement de s'arreter
*/
static int arret_demande;
/**
	boolean indiquant s'il faut attendre un timeout ou non
*/
static int timeout_on;
/**
variable représentant le timer qui capte qu'aucune pièce n'est arrivée depuis trop longtemps
*/
static int timer;
/**
type de pièce en cours de production (NUM_A ou NUM_B)
*/
static int type_piece_prod_courante;
/**
Numéro (dans le lot courant) du carton en cours de remplissage
*/
static int numero_carton_courant;
/**
nombre encore acceptable de pièce ne correspondant pas au dimensions attendues. 
Si le compteur touche 0, alors on bloque la mise en condition et on envois un message d'erreur.
*/
static int nb_mauvaise_piec_cart;
/**
Nombre de pièces encore attendues pour remplir le carton
*/
static int nb_piec_ok_cart;
/**
nombre de pièce encore nécéssaire pour le lot courant
*/
static int nb_piece_lot;
/**
mutex permettant d'empecher d'avoir plusieurs handler qui se lancent en même temps
*/
static pthread_mutex_t mut_handler_piece;
/**
boite au lettre dans laquelle le thread envois les erreurs qu'il rencontre
*/
static mqd_t bal_erreurs;
/**
boite au lettre dans laquelle le thread envois les cartons pleins
*/
static mqd_t bal_carton;
/**
boite au lettre de log
*/
static mqd_t bal_messages;

/*EXPORTED FUNCTIONS*/
/**
	* thread permettant de capturer les SIGUSR1 représentant les arrivées de carton et de les traiter
	*\param parameter parametre inutilsé
	*\return 0 quand le thread se termine correctement
*/
void* mise_en_carton(void *parameter) {
	// non utilisé
	(void) parameter;
	init_mise_en_carton();

	while (!arret_demande) {
		while (!arret_demande && (!timeout_on || timer < TIMEOUT)) {
			sleep(1);		
			timer++;
		}
		if (arret_demande) {
			break;
		}

		printf("[MiseEnCarton] Timeout! \n");

		struct message msg;
		msg.type = ERRPIECE;
		mq_send (bal_erreurs, (char *)&msg, sizeof(struct message), 1); //1 is priority

		sem_wait(&sem_err_timeout);

	}

	detruire_mise_en_carton();

	return 0;
}

/**
	* permet de charger les valeurs nécéssaire pour le thread de mise en carton
	*\param num_lot numéro du lot en cours de fabrication
*/
void charger_lot(int num_lot) {
	pthread_mutex_lock(&mut_handler_piece); //protection
	type_piece_prod_courante = num_lot;
	numero_carton_courant = 0;

	switch (num_lot) {
		case NUM_A :
			nb_piece_lot = g_nb_pal_a * g_nb_cart_pal_a * g_nb_piec_cart_a ;
			nb_piec_ok_cart  = g_nb_piec_cart_a;
			nb_mauvaise_piec_cart = ceil((double)g_seuil_defect_a * (double)g_nb_piec_cart_a / 10000.0);

			break;

		case NUM_B :
			nb_piece_lot = g_nb_pal_b * g_nb_cart_pal_b * g_nb_piec_cart_b ;
			nb_piec_ok_cart  = g_nb_piec_cart_b;
			nb_mauvaise_piec_cart = ceil((double)g_seuil_defect_b * (double)g_nb_piec_cart_b / 10000.0);

			break;
	}

	printf("nombre de pièces defectueuse acceptables: %d\n", nb_mauvaise_piec_cart); 
	timer = 0;

	pthread_mutex_unlock(&mut_handler_piece);
}


/**
	* supprime le carton courant et le remplace par un nouveau.
	* La fonction est mise en attente si une arrivée de pièce est actuellement traitée.
*/
void supprimer_carton_courant() {
	pthread_mutex_lock(&mut_handler_piece); //protection

	switch (type_piece_prod_courante) {
		case NUM_A :
			nb_piece_lot += g_nb_piec_cart_a - nb_piec_ok_cart; 
			nb_piec_ok_cart  = g_nb_piec_cart_a;
			nb_mauvaise_piec_cart = ceil((double)g_seuil_defect_a * (double)g_nb_piec_cart_a / 10000.0);

			break;

		case NUM_B :
			nb_piece_lot += g_nb_piec_cart_b - nb_piec_ok_cart; 
			nb_piec_ok_cart  = g_nb_piec_cart_b;
			nb_mauvaise_piec_cart = ceil((double)g_seuil_defect_b * (double)g_nb_piec_cart_b / 10000.0);

			break;
	}

	timer = 0;

	pthread_mutex_unlock(&mut_handler_piece);
}


/*LOCAL FUNCTIONS*/
/**
	* initialise les variables locales du thread ainsi que les message queue et inscrit le pid de l'application dans le shared memory.
*/
void init_mise_en_carton() {

	if ( pthread_mutex_init(&mut_handler_piece, NULL) != 0 ) {
		perror("[MiseEnCarton] Erreur creation mutex handler piece");
		exit(1);
	}
	signal(SIGUSR1, arrivee_piece_handler); 

	//ouverture des bal pour ce thread
	bal_erreurs = ouvrir_mqueue(BAL_ERREURS_NAME, O_WRONLY);
	bal_carton = ouvrir_mqueue(BAL_CARTONS_NAME, O_WRONLY);
	bal_messages = ouvrir_mqueue(BAL_MESSAGES_NAME, O_WRONLY);

	// On signal notre PID à la simu
	g_shm->pid_appli = getpid();
	timer = 0;
}

/**
	* gère l'arrivée d'une pièce.
	* Cette fonction lis les informations sur la pièce et sur des capteurs dans la shared memory.
	*\param parameter parametre inutilisé, il est juste là pour respecter le prototype du handler
*/
void arrivee_piece_handler(int parameter) {
	// non utilisé
	(void) parameter;
	
	int dim; 
	int dim_attendue;

	pthread_mutex_lock(&mut_handler_piece);

	timer = 0;
	dim = g_shm->dim; 
	dim_attendue = (type_piece_prod_courante == NUM_A) ? g_dim_a : g_dim_b;	

	if (dim == dim_attendue) {
		printf("[MiseEnCarton] piece OK\n");
		while (g_shm->carton == NOK) { //presence carton
			printf("[MiseEnCarton] probleme carton\n");
			struct message msg;
			msg.type = ERRCARTON;

			mq_send (bal_erreurs, (char *)&msg, sizeof(struct message), 1); //1 is priority

			sem_wait(&sem_carton_absent);
		} 

		nb_piece_lot--;
		if (nb_piece_lot == 0) { //lot fini
			printf("[MiseEnCarton] lot complet\n");
			g_shm->clapet = FALSE;
		}

		nb_piec_ok_cart--;
		if (nb_piec_ok_cart == 0) {
			printf("[MiseEnCarton] carton plein\n");
			int type_a = (type_piece_prod_courante == NUM_A);
			int nb_piece_def_acceptables = type_a ? ceil((double)g_seuil_defect_a * (double)g_nb_piec_cart_a / 10000.0): 
								ceil((double)g_seuil_defect_b * (double)g_nb_piec_cart_b / 10000.0);
			int nb_piece_defectueurses = nb_piece_def_acceptables - nb_mauvaise_piec_cart;
			struct message msg;
			msg.type = IMPRIMER;
			msg.params[0] = type_a ? g_num_lot_a : g_num_lot_b;
			msg.params[1] = numero_carton_courant;
			msg.params[2] = (type_a ? g_nb_piec_cart_a : g_nb_piec_cart_b);
			msg.params[3] = g_code_op;
			msg.params[4] = type_piece_prod_courante;
			msg.params[5] = nb_piece_defectueurses * 10000 / (type_a ? g_nb_piec_cart_a : g_nb_piec_cart_b);
	
			mq_send (bal_carton, (char *)&msg, sizeof(struct message), 1); //1 is priority
			mq_send (bal_messages, (char *)&msg, sizeof(struct message), 1); //1 is priority

			//passage au carton suivant
			numero_carton_courant++;
			nb_piec_ok_cart = type_a ? g_nb_piec_cart_a : g_nb_piec_cart_b;
			nb_mauvaise_piec_cart = (type_a ? ceil((double)g_seuil_defect_a * (double)g_nb_piec_cart_a / 10000.0) :
						ceil((double)g_seuil_defect_b * (double)g_nb_piec_cart_b / 10000.0));
			printf("[MiseEnCarton] carton envoyé à l'impression\n");
		}

	} else {
		printf("[MiseEnCarton] pièce defectueuse\n");
		nb_mauvaise_piec_cart--;
		if (nb_mauvaise_piec_cart <= 0) {
			printf("[MiseEnCarton] erreur trop de pièces defectueuses\n");

			struct message msg;
			msg.type = ERRDEFECT;
			mq_send (bal_erreurs, (char *)&msg, sizeof(struct message), 1); //1 is priority
		}
	}

	pthread_mutex_unlock(&mut_handler_piece);

}

/**
	* libère la mémoire en vue de detruire le processus
*/
void detruire_mise_en_carton() {
	printf("[MiseEnCarton] destruction\n");
	//blocage de la chaine
	//TODO: vérifier que la shm n'est pas détruite trop tot
	pthread_mutex_lock(&mut_handler_piece);
	g_shm->clapet = 0;
	pthread_mutex_unlock(&mut_handler_piece);

	//envoi du message de fin de prod à l'impression
	struct message msg;
	msg.type = ARRET;
	mq_send (bal_carton, (char *)&msg, sizeof(struct message), 1); //1 is priority

	//fermeture des boites au lettres
	mq_close(bal_erreurs);
	mq_close(bal_carton);
	mq_close(bal_messages);

	//destruction des mutex

	pthread_mutex_destroy(&mut_handler_piece);

	pthread_exit(0);
}

/**
	* lance le timer de timeout
*/
void launch_timeout() {
	timeout_on = TRUE;
	timer = 0;
}

/**
	* arrete le timer de timeout
*/
void stop_timeout() {
	timeout_on = FALSE;
}

/**
	* lance l'arret de la chaine de conditionnement. L'arret ne se fait pas forcément instantanément
*/
void demander_arret() {
	arret_demande = TRUE;
}

